#ifndef CLASSDAT_HPP
#define CLASSDAT_HPP
#include <algorithm>
#include <regex>
#include <map>
#include "DotFile.hpp"

static std::string replaceStr(const std::string& target,const std::string& replace,const std::string& to);

struct FunctionDesc
{
	std::string access;
	std::string type;
	std::string name;
	std::vector<std::string> param;
	std::string show()
	{
		std::string mark = (access == "public")? "+":(access == "protected")?"#": "-";
		return mark + name + "():" + replaceStr(replaceStr(type,"<","\\<"),">","\\>");
	}
};

struct MemberDataDesc
{
	std::string access;
	std::string type;
	std::string name;
	bool isClassType;
	std::string show()
	{
		std::string mark = (access == "public")? "+":(access == "protected")?"#": "-";
		return mark + name + ":" + replaceStr(replaceStr(type,"<","\\<"),">","\\>");
	}
};

struct Record
{
	static std::map<std::string,Record*> recordDB;
	std::vector<std::string> mBase;
	std::string name;
	std::vector<MemberDataDesc> data;
	std::vector<FunctionDesc> method;
	Record(const std::string& prmName):name(prmName){}
	static void maybeAddClass(const std::string& prmName)
	{
		auto itr = recordDB.find(prmName);
		if(itr == recordDB.end()) {
			recordDB[prmName] = new Record(prmName);
		}
	}
	static bool EquRecord(MemberDataDesc a,MemberDataDesc b)
	{
		return a.name == b.name;
	}
	static void printAsDot(bool showData, bool showDependency) {
		static DotFile Df("out.dot");
		for (auto&& elm: recordDB) {

			std::cout<<elm.second->name<<std::endl;

			if(showData){
				auto it_data = std::unique(elm.second->data.begin(),elm.second->data.end(),EquRecord);
				elm.second->data.resize(std::distance(elm.second->data.begin(),it_data));

				auto it_method = std::unique(elm.second->method.begin(), elm.second->method.end(),
					[](FunctionDesc& a,FunctionDesc& b) -> bool {
						return a.name == b.name;});
				elm.second->method.resize(std::distance(elm.second->method.begin(),it_method));

				std::stringstream d;
				d << elm.second->name << " [ label = \"{" << elm.second->name << "|";
				
				for (auto&& base : elm.second->mBase) {
					Df.append("edge [arrowhead = \"empty\"]\n");
					Df.append(elm.second->name + " -> " + base + "\n");
				}
			
				for (auto&& data : elm.second->data) {
					if(showDependency){
						if(data.isClassType) {
							Df.append("edge [arrowhead = \"open\"]\n");
							Df.append(elm.second->name + " -> " + data.type + "\n");
						}
						else {
							d << data.show() << "\\l";
						}
					}
					else{
						d << data.show() << "\\l";
					}
					
				}
				d << "|";
				for (auto&& method : elm.second->method) {
						d << method.show() << "\\l";
				}
				d << "}\"]" << std::endl;
				Df.append(d.str());
			}
			else{
				for (auto&& base : elm.second->mBase) {
					Df.append("edge [arrowhead = \"empty\"]\n");
					Df.append(elm.second->name + " -> " + base + "\n");
				}
			}
		}
	}

	static bool isTargetClassType(const std::string& belong)
	{
		auto itr = recordDB.find(belong);
		return (itr != recordDB.end())? true:false;
	}

	static void addField(const std::string& belong,const std::string& name,
			const std::string& type = "",const std::string& access = "",bool isClassType = false)
	{
		MemberDataDesc desc = {access,type,name,isClassType};
	
		for(auto i:recordDB[belong]->data){
			if(i.name==desc.name)
				return;
		}

		recordDB[belong]->data.push_back(desc);
	}

	static void addBase(const std::string& aBelong,const std::string& aBase)
	{
		for(auto i:recordDB[aBelong]->mBase){
			if(i==aBase)
				return;
		}
		::printf("add Base:%s\n",aBase.c_str());
		recordDB[aBelong]->mBase.push_back(aBase);
	}

	static void addMethod(const std::string& belong,const std::string& name,
			const std::string& type,const std::string& access) {
		FunctionDesc desc = {access,type,name};
		for(auto i:recordDB[belong]->method){
			if(i.name==desc.name)
				return;
		}

		recordDB[belong]->method.push_back(desc);
	}
};

static std::string replaceStr(const std::string& target,const std::string& replace,const std::string& to)
{
	std::string str = target;
	std::string::size_type pos = str.find(replace);
	while(pos != std::string::npos) {
		str.replace(pos,replace.size(),to);
		pos = str.find(replace,pos + to.size());
	}
	return str;
}

#endif
